<?php
/*
	Project:
		Brightcove Google Video SiteMap Generator

	File Version:
		0.1b

	Author(s):
		'Jay' John Anthony Jimenez - johnajimenez@gmail.com

	License:
		This script is release under the GPL licence.
		http://opensource.org/licenses/gpl-license.php

	Description:
		Builds a standard Google Video Sitemap

	Notes:
		Template Constructs:
		<VIDEO_LOOP></VIDEO_LOOP>
			All data in the above tag will output per video

		<TAG_LOOP></TAG_LOOP>
			All Data in the above will be output per tag (Up to Google's current
			limit) per video.

		Template Tokens:
		Most Tokens (with the exceptions detailed below) follow this pattern:
			{{[KEYWORD] [TOKEN]}}
		The tokens themselves are case sensitive:
		"shortDescription" is not the same as "ShortDescription"
		This is a limitation imposed on us by the BC API.

		Special Tokens:
		These tokens are the exceptions to the pattern above. They are exceptions
		because they do not come directly from BC or the Config. They are 
		rendered via logic or altered from their native form.
		{{vpl_url}}: Video Landing Page URL. Created based on video_url_pattern in config
		{{length_in_seconds}}: Video length in seconds. Altered from BC length which is milliseconds
		{{formatted_pub_date}}: Publication date. Altered from BC publishedDate to fit Google's Requirements
		{{formatted_exp_date}}: Expiration date. Altered from BC endDate to fit Google's Requirements
		{{formatted_start_date}}: Start date. Altered from BC startDate to fit Google's Requirements
		{{tag}}: The current tag inside the <TAG_LOOP>
		{{joined_tags}}: All tags, joined with a comma & [space].

		
		Keywords:
		"CONFIG": This allows acces to the values inside $pubisherConfigs below.
		"BC":     This allows access to the video specific values inside the BC
		          API.
		"BC_CF":  This grants access to the values inside customFields.

		Tokens:
		"CONFIG" tokens are detailed below under the heading "Publisher Configs"
		"BC" & "BC_CF" Tokens are detailed here:
			http://support.brightcove.com/en/docs/media-api-objects-reference
			under the "Video object" heading.
			The following API values are not currently available via tokens:
			"renditions", "FLVFullLength", "videoFullLength", "itemState",
			""
			If these are needed, contact the developer.
*/


// Publisher Configs:
// api_read_token: Read Token from BC > Account Settings > API Management
// publisher_id: BC Publisher ID for use with their clients side APIs
// player_loc_player_id: Player ID for Google to embed on their results
// video_url_pattern: Tokenized URL for individual video; What google will link to.
$pubisherConfigs = array(
	'api_read_token' => '',
	'publisher_id' => '',
	'player_loc_player_id' => '',
	'video_url_pattern' => 'http://howto.homedepot.com/videos/watch/{{BC id}}/{{BC name}}.html'
);

// Application Configs
// template_file: Path to the tokenized template to parse for SiteMap Generation.
// output_file: Path & Name for the finished SiteMap to be saved.
$appConfigs = array(
	'template_file'=>'./video_sitemap_template.txt',
	'output_file'=>'./hd_video_sitemap.xml'
);

// Allow for programatic limitations where possible.
// Each limit must be coded for below.
// tag_count: limit the <video:tag> to 32 as per google.
$limits = array(
	'tag_count'=> 32
);


////////////////////////////////////////////////////////////////////////////////
// Application Begins.
// If you are not a developer, you should not worry about what is happening
// down here.
////////////////////////////////////////////////////////////////////////////////

// Flags to prevent real work from occuring
$weAreDebugging = (isset($_REQUEST['only_debug']))?true:false;
	// Limits results to BC single call default

$weWantTheFile = false;
	// Does not write the file to disk, but shows it on screen

$showDebugMessages = (isset($_REQUEST['show_debug']))?true:false;
	// Chose if we should show debugging messages

debugMsg('Are we Debugging: ',$weAreDebugging);
debugMsg('Are we making a file?: ',$weWantTheFile);


////////////////////////////////////////////////////////////////////////////////
// Include the BC MAPI or die. No point in going on if it is not there.
if (!@include('includes/bc-mapi.php')) { die('Brightcove include not found. System Failed.'); }
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Never timeout!
////////////////////////////////////////////////////////////////////////////////
ini_set('max_execution_time', 0);
set_time_limit(0);

////////////////////////////////////////////////////////////////////////////////
// Enable Error Tracking
////////////////////////////////////////////////////////////////////////////////
ini_set('track_errors', 'true');

////////////////////////////////////////////////////////////////////////////////
// Prep the templates
////////////////////////////////////////////////////////////////////////////////
$templates = array();
$templates['full'] = @file_get_contents($appConfigs['template_file']);


if ($templates['full'] == '') {
	echo 'Failed to get the file '.$appConfigs['template_file'].' or file was empty.';
	exit;
}

// Only Minify it if we are doing a production run.
if (!$showDebugMessages) {
	$templates['full'] = str_replace(array("\n","\r","\r\n","\n\r","\t"),'',$templates['full']);	
}

// Replace all CONFIG level tokens here to prevent wastefully doing it in the loops
//$templates['full'] = preg_replace('/\{\{CONFIG (\w*)\}\}/ie','$pubisherConfigs["\1"]',$templates['full']);
$templates['full'] = preg_replace_callback('/\{\{CONFIG (\w*)\}\}/i',create_function('$matches', 'global $pubisherConfigs; return $pubisherConfigs[$matches[1]];'),$templates['full']);

// Get the Video Template
if (!preg_match('/<video_loop>(.*)<\/video_loop>/si',$templates['full'],$matches)){
	echo 'Failed to find VIDEO_LOOP in '.htmlspecialchars($templates['full']);
	exit;
}
// Strip the video loop out of the main template.
$templates['full'] = str_replace($matches[0],'{{_VIDEO_LOOP_REPLACEMENT_}}',$templates['full']);
// Store video loop into its own template.
$templates['videos'] = rtrim($matches[1]);

// Trash the matches to prevent contamination.
unset($matches);

// Let's see if there is a tags loop. This is not required, so we can skip if it
// doesn't exist.
if (preg_match('/[\n \t]*<tag_loop>(.*)<\/tag_loop>*/si',$templates['videos'],$matches)) {
	$parseTags = true;
	// Strip the video loop out of the main template.
	$templates['videos'] = str_replace($matches[0],'{{_TAG_LOOP_REPLACEMENT_}}',$templates['videos']);
	// Store video loop into its own template.
	$templates['tags'] = rtrim($matches[1]);
	unset($matches);

} else { // No tags desired in the sitemap. :(
	$parseTags = false;
}

debugMsg('Templates Array has: <br />',$templates);

////////////////////////////////////////////////////////////////////////////////
// Templates prepped
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Grab the videos
////////////////////////////////////////////////////////////////////////////////
$BCObj = new BCMAPI($pubisherConfigs['api_read_token']);

// These are the fields we want.
// We must do it this way in order to get the Custom Fields.
$videoFields = array(
	'video_fields' => 'id,name,accountId,shortDescription,longDescription,'.
	                  'creationDate,publishedDate,lastModifiedDate,linkURL,'.
	                  'linkText,tags,videoStillURL,thumbnailURL,referenceId,'.
	                  'length,economics,playsTotal,playsTrailingWeek,'.
	                  'customFields,FLVURL,startDate'
);

// If we are debugging only grab the first 100, otherwise grab them all.
// This will help save time and prevent possible charges from the BC side.
$theVideos = ($weAreDebugging)?@$BCObj->find('allVideos',$videoFields):@$BCObj->findAll('video',$videoFields);

if ($showDebugMessages) {
	echo 'From Brightcove, we got '.count($theVideos).' videos<br />';
	//debugMsg('$theVideos = <br />',$theVideos);
}


if (!$theVideos || count($theVideos)<1) {
	echo 'No videos returned.';
	exit;
}
////////////////////////////////////////////////////////////////////////////////
// Videos ready
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Start Building the SiteMap file contents
////////////////////////////////////////////////////////////////////////////////

// used to store all the video outputs from loop below.
$allVideoOutput = '';

foreach($theVideos as $k=>$thisVideo){
	if ($showDebugMessages) {
		echo 'Working on Video Number: '.$k.'<br />';
	}
	// Copy the template into a temp variable
	$thisVideoOutput = $templates['videos'];

	// Special Tokens:
	$special_s = $special_r = array();
		// URL
	$special_s[] = '{{vpl_url}}';
	//$special_r[] = xmlentities(preg_replace('/\{\{BC (\w*)\}\}/e','urlSanitize($thisVideo->\1);',$pubisherConfigs['video_url_pattern']));
	$special_r[] = xmlentities(preg_replace_callback('/\{\{BC (\w*)\}\}/',create_function('$matches', 'global $thisVideo; return (property_exists($thisVideo,$matches[1]))?urlSanitize($thisVideo->$matches[1]):"";'),$pubisherConfigs['video_url_pattern']));

		// Length in seconds
	$special_s[] = '{{length_in_seconds}}';
	$special_r[] = xmlentities(floor($thisVideo->length/1000));
		// Publish Date
	$special_s[] = '{{formatted_pub_date}}';
	$special_r[] = (property_exists($thisVideo,'publishedDate'))?(($thisVideo->publishedDate>1)?date('c',($thisVideo->publishedDate/1000)):''):'';
		// Exp Date
	$special_s[] = '{{formatted_exp_date}}';
	$special_r[] = (property_exists($thisVideo,'endDate'))?(($thisVideo->endDate>1)?date('c',($thisVideo->endDate/1000)):''):'';
		// Start Date
	$special_s[] = '{{formatted_start_date}}';
	$special_r[] = (property_exists($thisVideo,'startDate'))?(($thisVideo->startDate>1)?date('c',($thisVideo->startDate/1000)):''):"";

	if (is_array($thisVideo->tags)) {
		$hasTags = true;

			// Start Date
		$special_s[] = '{{joined_tags}}';
		$special_r[] = implode($thisVideo->tags, ', ');
	} else {
		$hasTags = false;
	}
	// Parse Special Tokens.
	$thisVideoOutput = str_replace($special_s, $special_r, $thisVideoOutput);

	// Parse the tags if needed
	if ($hasTags && $parseTags) {
		$tagOutput = '';
		foreach($thisVideo->tags as $count=>$thisTag){

			// Check the limit imposed by google.
			if (($count+1)<$limits['tag_count']) {
				$tagOutput .= str_replace('{{tag}}',xmlentities($thisTag),$templates['tags']);
			} else {
				break;
			}
		}
		// replace the internal tags placeholder with the processed tags
		$thisVideoOutput = str_replace('{{_TAG_LOOP_REPLACEMENT_}}',$tagOutput,$thisVideoOutput);
	}

	// Now to handle the BC Tokens:
	$thisVideoOutput = preg_replace_callback('/\{\{BC (\w*)\}\}/i', create_function('$matches', 'global $thisVideo; return (property_exists($thisVideo,$matches[1]))?xmlentities($thisVideo->$matches[1]):"";'), $thisVideoOutput);


	// Finally handle the BC_CF Tokens:
	$thisVideoOutput = preg_replace_callback('/\{\{BC_CF (\w*)\}\}/i', create_function('$matches', 'global $thisVideo; return (property_exists($thisVideo,"customFields") && property_exists($thisVideo->customFields,$matches[1]))?xmlentities($thisVideo->customFields->$matches[1]):"";'), $thisVideoOutput);

	// Now add this to the $allVideoOutput
	$allVideoOutput .= $thisVideoOutput;

	debugMsg('Output for this video:<br />',$thisVideoOutput);
}

// Now put all the video data into the main template 
$finalOutput = str_replace('{{_VIDEO_LOOP_REPLACEMENT_}}',$allVideoOutput,$templates['full']);

debugMsg('Final Output: <br />',$finalOutput);


////////////////////////////////////////////////////////////////////////////////
// Building the SiteMap file contents
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Output the data
////////////////////////////////////////////////////////////////////////////////
if ($weWantTheFile) {

	// Write the file.
	if (!$opFile = @fopen($appConfigs['output_file'],'w')) {
		echo 'Failed to open file for writing!';
		echo $php_errormsg.'<br />';
		exit;
	} else {
		if (!@fwrite($opFile, $finalOutput)) {
			echo 'Failed to write the file!<br />'.$appConfigs['output_file'];
			echo $php_errormsg.'<br />';
			exit;
		} else {
			@fclose($opFile);
			echo "SiteMap Created!<br /><a href='{$appConfigs['output_file']}'>Click Here.</a>";
		}
	}
} else {
	
	// If we are showing debug messages, output the data into a text area.
	if ($showDebugMessages) {
		echo '<textarea style="width:100%; height: 700px;" wrap="off">'.htmlspecialchars($finalOutput).'</textarea>';
	} else { // Otherwise output to screen.
		@header('Content-Type: text/xml');
		echo $finalOutput;
	
	}
}
////////////////////////////////////////////////////////////////////////////////
// Finished!
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Support Functions 
////////////////////////////////////////////////////////////////////////////////
// Altered to use the BC->sef() method. - John Jimenez
// urlSanitize
//		Replace any non-AlphaNum characters with a substitue character.
// $inStr: the String you want to sanitize
// $replacer: what to replace removed characters with, defaults to "_"
// $prevent_multiple_replacers: Replaces multiple consecutive instances of
//  	$replacer with a single instance. Defaults to false.
//function urlSanitize($inStr, $replacer = '_', $prevent_multiple_replacers = false){
	//$s = '/[^a-z0-9]/i';
	//$r = $replacer;
	//$inStr = preg_replace($s, $r, $inStr);
	//if ($prevent_multiple_replacers) {
		//$inStr = preg_replace('/('.preg_quote($r,'/').'){2,}/', $r, $inStr);
	//}
	//return $url;
//}

// Calls BC MAPI->sef() method, then strips consecutive dashes from the row.
function urlSanitize($inStr, $echoDebug = false) {
	global $BCObj;

	// Echo out the String that was passed.
	if ($echoDebug) {
		echo "urlSanitize was passed: ".$inStr."<br />";
	}

	// Use the brightcove sef instead of rolling my own.
	$inStr = $BCObj->sef($inStr);

	// Remove consecutive dashes 
	$inStr = preg_replace('/-{2,}/', '-', $inStr);

	// Echo out the String that was passed.
	if ($echoDebug) {
		echo "urlSanitize was is returning: ".$inStr."<br /><br />";
	}

	return $inStr;
}

// Simple debugger to spit out the variable passed.
function eDebug(&$var){
	echo '<pre>'.htmlspecialchars(print_r($var,1)).'</pre>';
}

function debugMsg($desc='', &$var) {
	global $showDebugMessages;
	if ($showDebugMessages) {
		echo $desc;
		eDebug($var);
		echo "<hr />";
		flush();
	}
}

// Sanitize the XML data of XML characters.
function xmlentities($string) {
	return str_replace(array("<", ">", "\"", "'", "&"), array("&lt;", "&gt;", "&quot;", "&apos;", "&amp;"), $string);
}

?>